// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using MonoDevelop.Core.Serialization;
using System.Text.RegularExpressions;

namespace MonoDevelop.EntifyEditor
{
	/// <summary>
	/// PropertyModel descibes one property of the entity. Property model contains
	/// all the information needed to generate property code into entity.
	/// </summary>
	[DataItem("Property")]
	public class Property
	{
		/// <summary>
		/// Constuctor for XML Serializer.
		/// </summary>
		private Property()
		{
			this.Name = "";
			this.Description = "";
			this.Index = false;
			this.Unique = false;
			this.Type = typeof(string).FullName;
			this.PersistentName = "";

			// String validation rules
			this.AllowNullValueForString = true;
			this.CheckStringLength = false;
			this.MatchToRegularExpression = false;
			this.MaximumLength = 0;
			this.MinimumLength = 0;
			this.RegularExperssion = "";

			// Numberic validation rules
			this.CheckNumericRange = false;
			this.MinimumNumbericValue = "0";
			this.MaximumNumericValue = "0";

			// DateTime validation rules
			this.CheckDateTimeRange = false;
			this.MinimumDate = DateTime.Today;
			this.MaximumDate = DateTime.Today;

			// Serializable validation rules
			this.AllowNullValueForSerializable = true;
		}

		/// <summary>
		/// Create new Property instance.
		/// </summary>
		public Property(string name)
		{
			this.Name = name;
			this.Description = "";
			this.Index = false;
			this.Unique = false;
			this.Type = typeof(string).FullName;
			this.PersistentName = name;

			// String validation rules
			this.AllowNullValueForString = true;
			this.CheckStringLength = false;
			this.MatchToRegularExpression = false;
			this.MaximumLength = 0;
			this.MinimumLength = 0;
			this.RegularExperssion = "";

			// Numberic validation rules
			this.CheckNumericRange = false;
			this.MinimumNumbericValue = "0";
			this.MaximumNumericValue = "0";

			// DateTime validation rules
			this.CheckDateTimeRange = false;
			this.MinimumDate = DateTime.Today;
			this.MaximumDate = DateTime.Today;

			// Serializable validation rules
			this.AllowNullValueForSerializable = true;
		}

		/// <summary>
		/// Name of the property. This is used as the property name in the generated entity class.
		/// </summary>
		[ItemProperty]
		public string Name
		{
			get;
			set;
		}
		
		/// <summary>
		/// Type of the property.
		/// </summary>
		[ItemProperty]
		public string Type
		{
			get;
			set;
		}
		
		/// <summary>
		/// Persistent name of the property. This must be unique within one entity type.
		/// Peristent name is used by persistent store when serializing the data.
		/// </summary>
		[ItemProperty]
		public string PersistentName
		{
			get;
			set;
		}
		
		/// <summary>
		/// Generic description text for property. This is added as a property comment
		/// when generating code, thus it will be displayed by code completion tools.
		/// </summary>
		[ItemProperty]
		public string Description
		{
			get;
			set;
		}
		
		/// <summary>
		/// Index name for the property. Property is included to given index.
		/// </summary>
		[ItemProperty]
		public bool Index
		{
			get;
			set;
		}
		
		/// <summary>
		/// Should uniqueness be enforced for this property. If property is unique
		/// then the property value must be unique within the context.
		/// </summary>
		[ItemProperty]
		public bool Unique
		{
			get;
			set;
		}

		/// <summary>
		/// Validation rule that defines is null value allowed for this property.
		/// </summary>
		[ItemProperty]
		public bool AllowNullValueForString
		{
			get;
			set;
		}

		/// <summary>
		/// Validation rule that defines should value match to specified regular experssion
		/// </summary>
		[ItemProperty]
		public bool MatchToRegularExpression
		{
			get;
			set;
		}

		/// <summary>
		/// Regular experssion used for validation.
		/// </summary>
		[ItemProperty]
		public string RegularExperssion
		{
			get;
			set;
		}

		/// <summary>
		/// Validation rule that defines should validator check string length.
		/// </summary>
		[ItemProperty]
		public bool CheckStringLength
		{
			get;
			set;
		}

		/// <summary>
		/// Minimum string length.
		/// </summary>
		[ItemProperty]
		public long MinimumLength
		{
			get;
			set;
		}

		/// <summary>
		/// Maximum string length.
		/// </summary>
		[ItemProperty]
		public long MaximumLength
		{
			get;
			set;
		}

		/// <summary>
		/// Should validator check that numeric value is within the specified range.
		/// </summary>
		[ItemProperty]
		public bool CheckNumericRange
		{
			get;
			set;
		}

		/// <summary>
		/// Minumum allowed numeric value for this property.
		/// </summary>
		[ItemProperty]
		public string MinimumNumbericValue
		{
			get;
			set;
		}

		/// <summary>
		/// Maximum allowed numeric value for this property.
		/// </summary>
		[ItemProperty]
		public string MaximumNumericValue
		{
			get;
			set;
		}

		/// <summary>
		/// Should validator check that datetime value is within the specified range.
		/// </summary>
		[ItemProperty]
		public bool CheckDateTimeRange
		{
			get;
			set;
		}

		/// <summary>
		/// Minimum allowed datetime.
		/// </summary>
		[ItemProperty]
		public DateTime MinimumDate
		{
			get;
			set;
		}

		/// <summary>
		/// Maximum allowed datetime.
		/// </summary>
		[ItemProperty]
		public DateTime MaximumDate
		{
			get;
			set;
		}

		/// <summary>
		/// Validation rule that defines is null value allowed for this property when type is serializable.
		/// </summary>
		[ItemProperty]
		public bool AllowNullValueForSerializable
		{
			get;
			set;
		}

		/// <summary>
		/// Is property valid for code generation.
		/// </summary>
		public bool IsValid
		{
			get
			{
				if(this.IsValidPropertyName() && this.Type != null && this.AreValidationRulesValid())
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}

		/// <summary>
		/// Is property name valid to be class name?
		/// </summary>
		public bool IsValidPropertyName()
		{
			var provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("C#");
			return provider.IsValidIdentifier(this.Name);
		}

		/// <summary>
		/// Checks are set validation rules valid for this property.
		/// </summary>
		private bool AreValidationRulesValid()
		{
			if(this.Type == typeof(string).FullName)
			{
				if(this.MatchToRegularExpression && this.RegularExperssion == null)
				{
					return false;
				}

				if(this.CheckStringLength)
				{
					if(this.MinimumLength < 0 || this.MaximumLength < this.MinimumLength)
					{
						return false;
					}
				}
			}
			else if(this.Type == typeof(Int16).FullName && this.CheckNumericRange)
			{
				try
				{
					Int16 min = Int16.Parse(this.MinimumNumbericValue);
					Int16 max = Int16.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(UInt16).FullName && this.CheckNumericRange)
			{
				try
				{
					UInt16 min = UInt16.Parse(this.MinimumNumbericValue);
					UInt16 max = UInt16.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(Int32).FullName && this.CheckNumericRange)
			{
				try
				{
					Int32 min = Int32.Parse(this.MinimumNumbericValue);
					Int32 max = Int32.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(UInt32).FullName && this.CheckNumericRange)
			{
				try
				{
					UInt32 min = UInt32.Parse(this.MinimumNumbericValue);
					UInt32 max = UInt32.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(Int64).FullName && this.CheckNumericRange)
			{
				try
				{
					Int64 min = Int64.Parse(this.MinimumNumbericValue);
					Int64 max = Int64.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(float).FullName && this.CheckNumericRange)
			{
				try
				{
					float min = float.Parse(this.MinimumNumbericValue);
					float max = float.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(double).FullName && this.CheckNumericRange)
			{
				try
				{
					double min = double.Parse(this.MinimumNumbericValue);
					double max = double.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(decimal).FullName && this.CheckNumericRange)
			{
				try
				{
					decimal min = decimal.Parse(this.MinimumNumbericValue);
					decimal max = decimal.Parse(this.MaximumNumericValue);
					if(min > max) return false;
				}
				catch
				{
					return false;
				}
			}
			else if(this.Type == typeof(DateTime).FullName)
			{
				if(this.CheckDateTimeRange && this.MinimumDate > this.MaximumDate)
				{
					return false;
				}
			}

			return true;
		}
	}
}
